<template>
  <div class="webglPrc3">
     <!-- <canvas>标记提供webgl绘制区域 -->
    <canvas ref="myGLCanvas" :width="rect?.width" :height="rect?.height"/>
  </div>
</template>

<script lang="ts">
import { Vue, Options } from 'vue-property-decorator';
import imageSrc from '../assets/speakAvatar2.png';
@Options({ components: {} })
export default class WebGlPrc3 extends Vue {
  public rect: DOMRect|null = null;
  public gl!: WebGLRenderingContext;
  public shaderProgram: WebGLProgram|null=null;
  public vertexBuffer: WebGLBuffer|null = null;
  public triangleVertexTextureCoordinateBuffer: WebGLBuffer|null = null;
  public vertexPositionAttribute=0;
  public vertexTextureAttributeLoc =0;
  public uniformSamplerLoc: WebGLUniformLocation|null=null;
  public woodTexture: WebGLTexture|null =null;
  public loadImage: HTMLImageElement|null =null;


  public draw() {
    this.gl.clear(this.gl.COLOR_BUFFER_BIT);// 通过参数this.gl.COLOR_BUFFER_BIT把颜色缓冲清除为事先用 this.gl.clearColor()定义的颜色
    if (this.loadImage) {
      this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
      this.gl.vertexAttribPointer(this.vertexPositionAttribute, 3, this.gl.FLOAT, false, 0, 0);

      this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.triangleVertexTextureCoordinateBuffer);
      this.gl.vertexAttribPointer(this.vertexTextureAttributeLoc, 2, this.gl.FLOAT, false, 0, 0);
      this.gl.bindTexture(this.gl.TEXTURE_2D, this.woodTexture);
      this.gl.drawArrays(this.gl.TRIANGLES, 0, 3);
    }

    requestAnimationFrame(() => {
      this.draw();
    });
  }

  public textureFinishedLoading(image: HTMLImageElement, texture: WebGLTexture) {
    this.gl.activeTexture(this.gl.TEXTURE0);// 开启0号纹理单元
    this.gl.bindTexture(this.gl.TEXTURE_2D, texture);// 将此纹理绑定为当前纹理
    // 配置纹理图像
    this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image);// 把纹理数据上传到GPU

    this.gl.generateMipmap(this.gl.TEXTURE_2D);
    // 下面两句调用 定义当前纹理的纹素和屏幕像素没有一对一的映射关系时纹理的映射方式 配置纹理参数
    // this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.NEAREST);// 纹理伸展
    // this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.NEAREST);// 纹理收缩
    // this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);//
    // this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);//

    // 将0号纹理传递给着色器
    this.gl.uniform1i(this.uniformSamplerLoc, 0);
  }
  public loadImageForTexture(url: string, texture: WebGLTexture) {
    const image = new Image();// 用来保存纹理数据
    image.onload = () => {
      this.loadImage = image;
      this.textureFinishedLoading(this.loadImage, texture);
    };
    // 当浏览器想要通过脚本使用另一个域的服务器的资源时，它在给服务器的请求中发送一个特殊的http报头
    // iamge.crossROrigin = "anonymous";
    image.src = url;
  }
  public setupTextures() {
    this.woodTexture = this.gl.createTexture();// 创建一个纹理对象
    this.loadImageForTexture(imageSrc, this.woodTexture!);
  }
  // 3.建立缓冲，用来保存顶点数据
  public setupBuffers() {
    this.vertexBuffer = this.gl.createBuffer(); // 创建并初始化一个用于储存顶点数据或着色数据的WebGLBuffer对象
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);// 把新建的WebGLBuffer对象绑定为当前的数组缓冲对象
    const triangleVertices = [
      0.0, 0.5, 0.0,
      -0.5, -0.5, 0.0,
      0.5, -0.5, 0.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(triangleVertices), this.gl.STATIC_DRAW);

    this.triangleVertexTextureCoordinateBuffer = this.gl.createBuffer();
    this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.triangleVertexTextureCoordinateBuffer);
    // 2D纹理坐标在x和y轴上，范围为0到1之间。
    const textureCoordinates = [
      0.5 + 0.5, 1.0,
      0.0 + 0.5, 0.0,
      1.0 + 0.5, 0.0,
    ];
    this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), this.gl.STATIC_DRAW);

    this.gl.enableVertexAttribArray(this.vertexPositionAttribute); // 打开属性数组列表中指定索引处的通用顶点属性数组 不用重复激活 一次就可以
    this.gl.enableVertexAttribArray(this.vertexTextureAttributeLoc);
  }
  public setupShaders() {
    // 1.创建着色器
    // 编写着色器源代码
    const vertexShaderSource = `precision mediump float;
                                attribute vec3 aVertexPosition;
                                attribute vec2 aTextureCoordinates;
                                varying vec2 vTextureCoordinates;
                                void main(){
                                  vTextureCoordinates = aTextureCoordinates;
                                  gl_Position = vec4(aVertexPosition,1.0);
                                }`;
    // gl_Position放置顶点坐标信息
    const fragmentShaderSource = `precision mediump float;
                                  varying vec2 vTextureCoordinates;
                                  uniform sampler2D uSampler;
                                  void main(){
                                    gl_FragColor = texture2D(uSampler,vTextureCoordinates);
                                    if(vTextureCoordinates.x>1. || vTextureCoordinates.x<0.) {
                                      gl_FragColor.a = 0.;
                                    }
                                  }`;
    // gl_FragColor设置当前片点的颜色

    // 根据源代码创建着色器
    const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fragmentShaderSource);
    // 2.创建程序对象和链接着色器
    // createProgram()创建和初始化一个 WebGLProgram 对象。一个 WebGLProgram 对象由两个编译过后的 WebGLShader 组成 - 顶点着色器和片段着色器
    this.shaderProgram = this.gl.createProgram();
    // attachShader()负责往 WebGLProgram 添加一个片段或者顶点着色器。
    this.gl.attachShader(this.shaderProgram!, vertexShader!);
    this.gl.attachShader(this.shaderProgram!, fragmentShader!);
    // inkProgram()方法链接给定的WebGLProgram，从而完成为程序的片元和顶点着色器准备GPU代码的过程。
    this.gl.linkProgram(this.shaderProgram!);
    // 在检查着色器链接状态的同时检查上下文是否丢失
    if (!this.gl.getProgramParameter(this.shaderProgram!, this.gl.LINK_STATUS) && this.gl.isContextLost()) {
      alert('Failed to setup shaders');
    }
    this.gl.useProgram(this.shaderProgram!);// 告诉webgl引擎可以用这个程序对象绘制图像
    // getAttribLocation() 方法返回了给定WebGLProgram对象中某属性的下标指向位置。 把顶点着色器使用的属性绑定到通用属性索引上
    this.vertexPositionAttribute = this.gl.getAttribLocation(this.shaderProgram!, 'aVertexPosition');
    this.vertexTextureAttributeLoc = this.gl.getAttribLocation(this.shaderProgram!, 'aTextureCoordinates');
    this.uniformSamplerLoc = this.gl.getUniformLocation(this.shaderProgram!, 'uSampler');
  }
  // 1.创建一个可以载入到GPU并且能够绘制几何图形的WebGL着色器
  public loadShader(type: number, shaderSource: any) {  // type 类型应该是什么
    const shader = this.gl.createShader(type);// 创建一个WebGLShader着色器对象
    this.gl.shaderSource(shader!, shaderSource);// 挂载GLSL源代码
    this.gl.compileShader(shader!);// 完成着色器的编译

    // 检查编译的状态，如果出现错误，发出警告消息，并删除着色器对象
    // 在检查着色器编译结果的同时检查上下文是否丢失
    if (!this.gl.getShaderParameter(shader!, this.gl.COMPILE_STATUS) && !this.gl.isContextLost()) {
      alert(`Error Compiling shader${this.gl.getShaderInfoLog(shader!)}`);
      this.gl.deleteShader(shader);
      return null;
    }
    return shader;
  }
  public mounted() {
    this.rect = (this.$el as HTMLDivElement).getBoundingClientRect();
    this.gl = (this.$refs.myGLCanvas as HTMLCanvasElement).getContext('webgl') as WebGLRenderingContext;
    // 用于图像预处理 如果为true，则把图片上下对称翻转坐标轴(图片本身不变)。原因是image对象和webgl纹理对象的坐标系不同
    this.gl.pixelStorei(this.gl.UNPACK_FLIP_Y_WEBGL, true);
    this.gl.viewport(0, 0, this.rect!.width, this.rect!.height);// 定义一个视口
    this.setupShaders();
    this.setupBuffers();
    this.setupTextures();
    this.gl.clearColor(1, 1, 1, 1); // 用于设置清空颜色缓冲时的颜色值
    this.draw();
  }
}
// 载入纹理的过程：
// 1.创建WebGLTexture对象
// 2.绑定纹理
// 3.载入图像数据（把纹理数据上传到GPU或者GPU可以访问的内存）
// 4.将纹理上传到GPU
// 5.定义纹理参数
//

// 处理丢失上下文问题时需要考虑的几个因素：
// 1.不要给webgl资源对象添加新的属性，，，一个方法是给另一个不是有webglAPI创建的全局对象添加属性
// 2.在检查着色器编译结果的同时检查丢失上下文
// 3.在检查着色器链接状态的同时检查上下文是否丢失
</script>

<style lang="postcss" scoped>
.webglPrc3{
    /* 为什么不加定位 就不会在页面显示 */
    touch-action: none;
    position: absolute;
    left: 0;top: 0;
    width: 100%;
    height: 100%;
    background-color: grey;
}
</style>
